import {
	deepMerge,
	isFunction,
	urlCombine,
	isNumber,
	isArray,
	isObject,
	isPromise,
	isString,
	parseParamObjectToUrlQueryString
} from "@/uni_modules/pure-utils";

export default class PureRequest {
	// 构造函数
	constructor(config) {
		// 默认配置
		const defaultConfig = {
			/**
			 * [uni.request() 自带配置](https://uniapp.dcloud.net.cn/api/request/request.html)
			 */
			url: "", // 接口地址，可以是绝对路径或者相对路径，如果是相对路径，最终会和 baseURL 合并成一个完整的 URL
			data: {}, // 请求的参数
			header: {}, // 设置请求的 header, header 中不能设置 Referer
			method: "GET", // 请求类型，GET | POST | PUT | DELETE | CONNECT | HEAD | OPTIONS | TRACE | UPLOAD（扩展的）
			timeout: 30000, // 超时时间，单位毫秒
			dataType: "json", // 告诉服务器，我要发送的数据类型，如果设为 json, 会对返回的数据进行一次 JSON.parse, 非 json 不会进行 JSON.parse
			responseType: "text", // 设置响应的数据类型，text | arraybuffer
			sslVerify: true, // 是否验证 ssl 证书？
			withCredentials: false, // 跨域请求时是否携带凭证（cookies）？
			firstIpv4: false, // DNS 解析时是否优先使用 ipv4？
			enableHttp2: false, // 是否开启 http2？
			enableQuic: false, // 是否开启 quic？
			enableCache: false, // 是否开启 cache？
			enableHttpDNS: false, // 是否开启 HttpDNS 服务?
			httpDNSServiceId: "", // HttpDNS 服务商 Id
			enableChunked: false, // 是否开启 transfer-encoding chunked？
			forceCellularNetwork: false, // 是否在 wifi 下使用移动网络发送请求？
			enableCookie: false, // 开启后可在 headers 中编辑 cookie
			cloudCache: false, // 是否开启云加速？
			defer: false, // 控制当前请求是否延时至首屏内容渲染后发送

			/**
			 * [uni.upload() 自带配置](https://uniapp.dcloud.net.cn/api/request/network-file.html)
			 */
			files: null, // 需要上传的文件列表。使用 files 时，filePath 和 name 不生效
			fileType: "image", // 文件类型，image/video/audio
			file: null, // 要上传的文件对象
			filePath: null, // 要上传文件资源的路径
			name: "file", // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
			formData: {}, // HTTP 请求中其他额外的 form data

			/**
			 * 扩展配置
			 */
			params: null, // 请求参数，会合并到 url 中
			// 表示成功的请求状态码
			// Number | Array | Object = {start: 200, end: 300}
			// 数字： 验证状态码是否等于这个数字
			// 数组： 验证状态码是否在这个数组中
			// 对象： 验证状态码是否在这个对象的范围内
			successStatusCode: 200,
			// 表示成功的数据状态码
			successDataCode: 0,
			// 接口基准前缀(一般为接口域名地址)
			baseURL: "",
			// 请求前的处理函数，参数为此次请求配置(config)，返回 null 则中断请求
			before: null,
			// 响应后的处理函数，参数为 response
			after: null,
			// 是否显示 loading
			loading: true,
			// 请求超过此值设置的毫秒数后才会显示，设置为 0 会在请求后立即显示
			loadingDelay: 800,
			// 提示内容
			loadingTips: "",
			// 是否显示透明蒙层，防止触摸穿透
			loadingMask: true,
			// 是否显示错误提示
			error: true,
			// 错误提示内容
			errorTips: "",
			// 是否显示透明蒙层，防止触摸穿透
			errorMask: true,
			// 是否显示成功提示
			success: false,
			// 成功提示内容
			successTips: "",
			// 是否显示透明蒙层，防止触摸穿透
			successMask: true
		};

		// 合并配置
		this.config = deepMerge(defaultConfig, config);

		// 任务队列
		this.tasks = {};
	}

	/**
	 * 设置配置
	 * @param {Object} config 配置
	 */
	setConfig(conf) {
		this.config = deepMerge(this.config, conf);
	}

	// 设置请求前的处理函数
	setBefore(before) {
		this.config.before = before;
	}

	// 设置响应后的处理函数
	setAfter(after) {
		this.config.after = after;
	}

	// 设置Header
	setHeader(header) {
		this.config.header = deepMerge(this.config.header, header);
	}

	// 清除Header
	clearHeader() {
		this.config.header = {};
	}

	/**
	 * GET 请求
	 * @param {Object} conf 本次请求的配置
	 */
	get(conf) {
		conf.method = "GET";
		return this.request(conf);
	}

	/**
	 * POST 请求
	 * @param {Object} conf 本次请求的配置
	 */
	post(conf) {
		conf.method = "POST";
		return this.request(conf);
	}

	/**
	 * upload 请求
	 * @param {Object} conf 本次请求的配置
	 */
	upload(conf) {
		conf.method = "UPLOAD";
		return this.request(conf);
	}

	/**
	 * PUT 请求
	 * @param {Object} conf 本次请求的配置
	 */
	put(conf) {
		conf.method = "PUT";
		return this.request(conf);
	}

	/**
	 * DELETE 请求
	 * @param {Object} conf 本次请求的配置
	 */
	delete(conf) {
		conf.method = "DELETE";
		return this.request(conf);
	}

	/**
	 * CONNECT 请求
	 * @param {Object} conf 本次请求的配置
	 */
	connect(conf) {
		conf.method = "CONNECT";
		return this.request(conf);
	}

	/**
	 * HEAD 请求
	 * @param {Object} conf 本次请求的配置
	 */
	head(conf) {
		conf.method = "HEAD";
		return this.request(conf);
	}

	/**
	 * OPTIONS 请求
	 * @param {Object} conf 本次请求的配置
	 */
	options(conf) {
		conf.method = "OPTIONS";
		return this.request(conf);
	}

	/**
	 * TRACE 请求
	 * @param {Object} conf 本次请求的配置
	 */
	trace(conf) {
		conf.method = "TRACE";
		return this.request(conf);
	}

	/**
	 * 请求
	 * @param {Object} conf 本次请求的配置
	 */
	async request(conf) {
		// 合并配置，得到本次请求的最终配置
		let _conf = deepMerge(this.config, conf);

		// 请求前的处理函数
		if (isFunction(_conf?.before) && !_conf.before(_conf)) {
			let beforeResult = _conf.before(_conf);

			// 返回了一个 Promise
			if (isPromise(afterResult)) {
				beforeResult = await beforeResult;
			}

			if (beforeResult === null) {
				const error = {
					code: -1000,
					errMsg: "请求被拦截"
				};
				return Promise.reject(error);
			}
		}

		// 显示 loading
		this.showLoading(_conf);

		// 完整的请求地址
		_conf.url = urlCombine(_conf.baseURL, _conf.url);

		// 加上 params 参数
		if (_conf?.params) {
			const queryString = parseParamObjectToUrlQueryString(_conf.params);
			// 判断 _conf.url 是否包含 ?
			if (_conf.url.includes("?")) _conf.url += `&${queryString}`;
			else _conf.url += `?${queryString}`;
		}

		// 请求方法。request | upload
		const uniMethod = _conf.method.toUpperCase() === "UPLOAD" ? uni.uploadFile : uni.request;

		// 返回 Promise
		return new Promise((resolve, reject) => {
			// 任务id
			const taskId = Symbol();

			this.tasks[taskId] = uniMethod({
				..._conf,
				success: async (res) => {
					// 返回的数据是字符串时，尝试转换成json对象
					if (isString(res?.data) && _conf?.dataType === "json") {
						try {
							res.data = JSON.parse(res?.data);
						} catch (error) {
							console.error("解析 JSON字符串失败", error);
						}
					}

					// 请求后的处理函数
					if (isFunction(_conf?.after)) {
						res.config = _conf;
						let afterResult = _conf.after(res);

						// 返回了一个 Promise
						if (isPromise(afterResult)) {
							afterResult = await afterResult;
						}

						if (afterResult !== null) return afterResult;
					}

					// 检查状态码是否成功
					if (this.checkSuccessByStatusCode(res.statusCode) && this.checkSuccessByDataCode(res?.data?.code)) {
						// 显示成功提示
						this.showSuccess(_conf, res);
						resolve(res?.data || res);
					} else {
						// 显示错误提示
						this.showError(_conf, res);
						reject(res?.data || res);
					}
				},
				fail: (err) => {
					// 显示错误提示
					this.showError(_conf, err);
					reject({
						code: -4000,
						...err
					});
				},
				complete: () => {
					// 关闭 loading
					this.closeLoading(_conf);

					// 清除任务
					delete this.tasks[taskId];
				}
			});
		});
	}

	/**
	 * 中断并清除所有请求任务（tasks）
	 */
	aborts() {
		for (let taskId of this.tasks) {
			this.tasks[taskId].abort();
			delete this.tasks[taskId];
		}
	}

	/**
	 * 显示 loading
	 * @param {Object} config 请求配置
	 */
	showLoading(config) {
		// 不用显示loading
		if (!config.loading) return;

		// loading 定时器
		config.loadingTimer = setTimeout(() => {
			// loading 实例
			config.loadingInstance = uni.showLoading({
				title: config?.loadingTips || "",
				mask: config?.loadingMask
			});
		}, config?.loadingDelay || 0);
	}

	/**
	 * 关闭 loading
	 * @param {Object} config 请求配置
	 */
	closeLoading(config) {
		// 不用关闭loading
		if (!config.loadingTimer) return;

		// 清除定时器
		clearTimeout(config.loadingTimer);
		config.loadingTimer = null;

		// 清除 loading 实例
		if (config.loadingInstance) {
			uni.hideLoading();
			config.loadingInstance = null;
		}
	}

	/**
	 * 检查请求状态码是否成功
	 * @param {Number} statusCode 状态码
	 * @returns {Boolean}
	 */
	checkSuccessByStatusCode(code) {
		// 表示成功的请求状态码
		const successStatusCode = this.config.successStatusCode;

		// 配置的请求状态码是数字
		if (isNumber(successStatusCode)) {
			return successStatusCode === code;
		}

		// 配置的状态码是数组
		if (isArray(successStatusCode)) {
			return configSuccessCode.includes(code);
		}

		// 配置的状态码是对象
		if (isObject(successStatusCode)) {
			return code >= (successStatusCode?.start || 200) && code < (successStatusCode?.end || 300);
		}

		return false;
	}

	/**
	 * 检查数据状态码是否成功
	 * @param {Object} res 响应数据
	 * @returns {Boolean}
	 */
	checkSuccessByDataCode(code) {
		// 表示成功的数据状态码
		const successDataCode = this.config.successDataCode;

		// 配置的数据状态码是数字
		if (isNumber(successDataCode)) {
			return successDataCode === code;
		}

		// 配置的数据状态码是数组
		if (isArray(successDataCode)) {
			return successDataCode.includes(code);
		}

		// 配置的数据状态码是对象
		if (isObject(successDataCode)) {
			return code >= (successDataCode?.start || 200) && code < (successDataCode?.end || 300);
		}

		return false;
	}

	/**
	 * 显示错误提示
	 * @param {Object} config 请求配置
	 * @param {Object} res 响应数据
	 */
	showError(config, res) {
		// 不用显示错误提示
		if (!config.error) return;

		// 错误提示内容
		let errorTips =
			config?.errorTips || res?.data?.msg || res?.data?.message || res?.data?.errMsg || res?.errMsg || "请求失败";

		// 显示错误提示
		uni.showToast({
			title: errorTips,
			icon: "none",
			duration: 2500
		});
	}

	/**
	 * 显示成功提示
	 * @param {Object} config 请求配置
	 * @param {Object} res 响应数据
	 */
	showSuccess(config, res) {
		// 不用显示成功提示
		if (!config.success) return;

		// 成功提示内容
		let successTips =
			config?.successTips ||
			res?.data?.msg ||
			res?.data?.message ||
			res?.data?.errMsg ||
			res?.errMsg ||
			"请求成功";

		// 显示成功提示
		uni.showToast({
			title: successTips,
			icon: "none",
			duration: 2500
		});
	}
}
